#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MY_PORT 61000        // 使用相同的端口号

#if 0
#include <stdio.h>
#include <unistd.h>    /*man 2 close*/
#include <sys/types.h> /* man 2 socket */
#include <sys/socket.h>
#include <netinet/in.h> /*man 3 inet_addr*/
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>

#define GROUP_PORT 61000

/*
    实现udp组播的功能：重点是在接收端
*/
int main(int argc, char **argv)
{
    // 创建套接字
    // AF_INET:ipv4地址族 SOCK_DGRAM:udp数据报协议
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_fd < 0)
    {
        printf("socket udp broadcast fail\n");
        return -1;
    }

    int ret = 0;

    // 定义组播结构体
    struct ip_mreq vmreq;
    // 对结构体进行初始化
    inet_pton(AF_INET, "224.0.0.10", &vmreq.imr_multiaddr);   // 组播地址
    //(这句话不写就是默认本机任意ip地址:也可以收到发送端发给组播地址的数据;除非IP地址写错就收不到了)
    // inet_pton(AF_INET, "192.168.2.48", &vmreq.imr_interface); // 需要添加ubunut的ip到组的ip
    // inet_pton(AF_INET, "192.168.2.47", &vmreq.imr_interface); // 需要添加ubunut的ip到组的ip
    // 加入组播属性（也就是设置这个套接字 可以接收组播信息）
    setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &vmreq, sizeof(vmreq));

    // 给广播地址发送数据
    struct sockaddr_in my_addr; // struct sockaddr_in是新的结构体
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(GROUP_PORT);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); //
    ret = bind(socket_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_in));
    if (ret < 0)
    {
        printf("bind group fail\n");
        return -1;
    }
    printf("bind group success\n");

    char buf[1024] = {0};

    while (1)
    {
        memset(buf, 0, sizeof(buf)); // 切记
        // 定义一个发送端的地址
        struct sockaddr_in send_addr;
        socklen_t addrlen = sizeof(struct sockaddr_in);
        ret = recvfrom(socket_fd, buf, sizeof(buf), 0, (struct sockaddr *)&send_addr, &addrlen);
        char *ip = inet_ntoa(send_addr.sin_addr);
        int port = ntohs(send_addr.sin_port);
        printf("recvfrom broadcast [%s][%d]buf:%s ret:%d\n", ip, port, buf, ret);
    }

    // 关闭套接字
    close(socket_fd);

    return 0;
}
#endif

#if 1
int main(int argc, char **argv)
{
    // 建立套接字
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_fd < 0) {
        perror("socket failed");
        return -1;
    }

    // 设置端口复用
    int optval = 1;
    setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    // 设置允许接收广播
    setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));

    // 绑定到任意地址(INADDR_ANY)而不是特定IP
    struct sockaddr_in my_addr;
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(MY_PORT);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 关键修改
    
    if (bind(socket_fd, (struct sockaddr *)&my_addr, sizeof(my_addr)) < 0) {
        perror("bind failed");
        close(socket_fd);
        return -1;
    }

    printf("Waiting for broadcast messages on port %d...\n", MY_PORT);

    // 持续接收消息
    while (1) 
    {
        char buf[1024] = {0};
        struct sockaddr_in recv_addr;
        socklen_t addrlen = sizeof(recv_addr);
        
        int ret = recvfrom(socket_fd, buf, sizeof(buf), 0,
                          (struct sockaddr *)&recv_addr, &addrlen);
        if (ret < 0) {
            perror("recvfrom failed");
            continue;
        }

        // 打印发送方信息和消息内容
        char *ip = inet_ntoa(recv_addr.sin_addr);
        int port = ntohs(recv_addr.sin_port);
        printf("[%s:%d] %s\n", ip, port, buf);
    }

    close(socket_fd);
    return 0;
}
#endif